package com.ds.infrastructure.hyperspace.container.internal.opentracing.impl;

import com.ds.infrastructure.hyperspace.common.config.OpentracingConfig;
import com.ds.infrastructure.hyperspace.container.flow.context.HyperspaceDuplexFlowContext;
import com.ds.infrastructure.hyperspace.container.internal.opentracing.reporter.IOpentracingReporter;
import io.netty.handler.codec.http.HttpHeaderNames;
import io.netty.handler.codec.http.HttpRequest;
import io.netty.handler.codec.http2.Http2Headers;
import io.netty.util.internal.StringUtil;
import lombok.Getter;

/**
 * @Author: yaozh
 * @Description:
 */
public class HyperspaceTracerContext {
    @Getter
    private final String tracerId;
    private final String gatewayLocalAddress;
    private final String gatewayRemoteAddress;
    private String targetRemoteAddress;
    @Getter
    private TracerSpan gatewaySpan;
    private TracerSpan targetSpan;

    public HyperspaceTracerContext(String localAddress, String remoteAddress, HttpRequest httpRequest) {
        gatewayLocalAddress = localAddress;
        gatewayRemoteAddress = remoteAddress;
        gatewaySpan = HyperspaceTracerUtil.extract(httpRequest);
        tracerId = gatewaySpan.getTraceId();
        String host = httpRequest.headers().get(HttpHeaderNames.HOST);
        String operationName = host + httpRequest.uri();
        String methodName = httpRequest.method().name();
        initGatewaySpan(host, operationName, methodName);
    }

    public HyperspaceTracerContext(String localAddress, String remoteAddress, Http2Headers http2Headers) {
        gatewayLocalAddress = localAddress;
        gatewayRemoteAddress = remoteAddress;
        gatewaySpan = HyperspaceTracerUtil.extract(http2Headers);
        tracerId = gatewaySpan.getTraceId();
        String host = http2Headers.authority().toString();
        String operationName = host + http2Headers.path().toString();
        String methodName = http2Headers.method().toString();
        initGatewaySpan(host, operationName, methodName);
    }

    private void initGatewaySpan(String host, String operationName, String methodName) {
        gatewaySpan.setOperationName(operationName);
        gatewaySpan
                .setTag(TracerTags.COMPONENT, TracerConstants.COMPONENT)
                .setTag(TracerTags.KEPLER_AGENT_APPLICATION_NAME, TracerConstants.APPLICATION_NAME)
                .setTag(TracerTags.KEPLER_AGENT_TIER_NAME, TracerConstants.TIER_NAME)
                .setTag(TracerTags.KEPLER_AGENT_TOKEN, getAgentToken())
                .setTag(TracerTags.SPAN_ACTIVE, true)
                .setTag(TracerTags.REMOTE_CALL_ROLE, TracerConstants.SERVER_ROLE)
                .setTag(TracerTags.REMOTE_CALL_ENDPOINT, operationName)
                .setTag(TracerTags.HTTP_METHOD, methodName)
                .setTag(TracerTags.SERVICE_DOMAIN, host);
        if (isSetSampledValue()) {
            gatewaySpan.setBaggageItem(TracerConstants.BAGGAGE_KEY_ZIPKIN_SAMPLED, TracerConstants.BAGGAGE_VALUE_SAMPLED);
        }
        String agentParentToken = this.gatewaySpan.getBaggageItem(TracerConstants.BAGGAGE_KEY_AGENT_TOKEN);
        if (!StringUtil.isNullOrEmpty(agentParentToken)) {
            gatewaySpan.setTag(TracerTags.KEPLER_AGENT_PARENT_TOKEN, agentParentToken);
        }
        this.gatewaySpan.setBaggageItem(TracerConstants.BAGGAGE_KEY_AGENT_TOKEN, getAgentToken());
    }

    public void inject(String remoteAddress, HttpRequest httpRequest) {
        targetRemoteAddress = remoteAddress;
        if (targetSpan == null) {
            String host = httpRequest.headers().get(HttpHeaderNames.HOST);
            String operationName = host + httpRequest.uri();
            String methodName = httpRequest.method().name();
            initTargetSpan(host, operationName, methodName);
        }
        HyperspaceTracerUtil.inject(targetSpan, httpRequest);
    }

    public void inject(String remoteAddress, Http2Headers http2Headers) {
        targetRemoteAddress = remoteAddress;
        if (targetSpan == null) {
            String host = http2Headers.authority().toString();
            String operationName = host + http2Headers.path().toString();
            String methodName = http2Headers.method().toString();
            initTargetSpan(host, operationName, methodName);
        }
        HyperspaceTracerUtil.inject(targetSpan, http2Headers);
    }

    private void initTargetSpan(String host, String operationName, String methodName) {
        targetSpan = new TracerSpan(gatewaySpan.getSpanId(), gatewaySpan.getTraceId(),
                HyperspaceTracerUtil.nextId(), gatewaySpan.getBaggage());
        targetSpan.setOperationName(operationName);
        targetSpan
                .setTag(TracerTags.COMPONENT, TracerConstants.COMPONENT)
                .setTag(TracerTags.KEPLER_AGENT_APPLICATION_NAME, TracerConstants.APPLICATION_NAME)
                .setTag(TracerTags.KEPLER_AGENT_TIER_NAME, TracerConstants.TIER_NAME)
                .setTag(TracerTags.KEPLER_AGENT_TOKEN, getAgentToken())
                .setTag(TracerTags.REMOTE_CALL_ROLE, TracerConstants.CLIENT_ROLE)
                .setTag(TracerTags.REMOTE_CALL_ENDPOINT, operationName)
                .setTag(TracerTags.HTTP_METHOD, methodName)
                .setTag(TracerTags.SERVICE_DOMAIN, host);
    }

    public void finished(HyperspaceDuplexFlowContext duplexFlowContext, IOpentracingReporter opentracingReporter) {
        if (duplexFlowContext.getFlowHandlerPipeline() == null
                || !gatewaySpan.isSampled() || gatewaySpan.isFinish()) {
            return;
        }
        addGatewaySpanTags(duplexFlowContext);
        if (targetSpan != null) {
            addTargetSpanTags(duplexFlowContext);
            targetSpan.finish();
            opentracingReporter.save(targetSpan);
        }
        gatewaySpan.finish();
        opentracingReporter.save(gatewaySpan);
    }

    private void addTargetSpanTags(HyperspaceDuplexFlowContext duplexFlowContext) {
        targetSpan
                .setTag(TracerTags.REMOTE_CALL_ADDRESS, targetRemoteAddress)
                .setTag(TracerTags.REMOTE_CALL_RESPONSE_STATUS, duplexFlowContext.getTargetResponseCode())
                .setTag(TracerTags.REMOTE_CALL_PROTOCOL, duplexFlowContext.getEndpointProtocol().name());
    }

    private void addGatewaySpanTags(HyperspaceDuplexFlowContext duplexFlowContext) {
        gatewaySpan
                .setTag(TracerTags.REMOTE_CALL_ADDRESS, gatewayLocalAddress)
                .setTag(TracerTags.REMOTE_CALL_RESPONSE_STATUS, duplexFlowContext.getEntryResponseCode())
                .setTag(TracerTags.REMOTE_CALL_PROTOCOL, duplexFlowContext.getEndpointProtocol().name())
                .setTag(TracerTags.REMOTE_CALL_REQUEST_IP, gatewayRemoteAddress);
    }

    private String getAgentToken() {
        return gatewayLocalAddress;
    }

    private boolean isSetSampledValue() {
        if (gatewaySpan.getBaggage().containsKey(TracerConstants.BAGGAGE_KEY_ZIPKIN_SAMPLED)) {
            return false;
        }
        return TracerConstants.NO_PARENT_ID.equals(gatewaySpan.getParentId());
    }

    public void sample(long requestStartTime, OpentracingConfig opentracingConfig) {
        if (TracerConstants.NO_PARENT_ID.equals(gatewaySpan.getParentId())
                && requestStartTime % OpentracingConfig.SAMPLE_RATE_MAX > opentracingConfig.getSampleRateThreshold()) {
            gatewaySpan.setBaggageItem(TracerConstants.BAGGAGE_KEY_ZIPKIN_SAMPLED, TracerConstants.BAGGAGE_VALUE_NO_SAMPLED);
        }
    }
}
